package com.csun.cmny.provider.device.phone;

import com.csun.cmny.provider.device.phone.entity.PhoneConstant;
import com.csun.cmny.provider.device.phone.entity.PhoneDataEntity;
import com.csun.cmny.provider.device.phone.entity.PhoneMessageDataEntity;
import com.csun.cmny.provider.model.domain.DmcPhoneEvent;
import com.csun.cmny.provider.model.enums.device.DeviceStatusEnum;
import com.csun.cmny.util.PublicUtil;
import com.csun.cmny.util.RedisKeyUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.*;

/**
 * @Auther: chisj chisj@foxmal.com
 * @Date: 2019-02-19 09:22
 * @Description: the class phone netty tcp server handler
 */
@Slf4j
@Component
@ChannelHandler.Sharable
public class PhoneNettyTcpServerHandler extends ChannelInboundHandlerAdapter {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private PhoneMessageDecoder phoneMessageDecoder;

    @Resource
    private PhoneMessageHandler phoneMessageHandler;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ChannelFuture f = null;
        try {
            ByteBuf in = (ByteBuf) msg;
            byte[] receiveMsg = new byte[in.readableBytes()];
            in.readBytes(receiveMsg);

            log.info("===>       receiveMsg: {}", format(Hex.encodeHexString(receiveMsg)));

            PhoneMessageDataEntity phoneMsg = phoneMessageDecoder.parse(receiveMsg);
            if (phoneMsg == null) {
                log.warn("phone message data entity parse failed.");
                return ;
            }

            // 回复消息
            //f = ctx.writeAndFlush(Unpooled.copiedBuffer(PhoneConstant.PHONE_RESPONSE));
            //if (f.isDone()) {
                // 处理消息
                phoneMessageHandler.handler(ctx, phoneMsg);
            //}
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("===>  exceptionCaught: {}");
        dealPhoneOff(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //ctx.fireChannelInactive();
        log.error("===>  channelInactive: {}");
        dealPhoneOff(ctx);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        log.error("===>  channelRegistered: {}");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        log.error("===>  channelUnregistered: {}");
    }


    /**
     * 这里是保持服务器与客户端长连接  进行心跳检测 避免连接断开
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if(evt instanceof IdleStateEvent) {
            IdleStateEvent stateEvent = (IdleStateEvent) evt;
            PingWebSocketFrame ping = new PingWebSocketFrame();
            switch (stateEvent.state()){
                //读空闲（服务器端）
                case READER_IDLE:
                    log.info("【"+ctx.channel().remoteAddress()+"】读空闲（服务器端）");
                    ctx.writeAndFlush(ping);
                    break;
                //写空闲（客户端）
                case WRITER_IDLE:
                    log.info("【"+ctx.channel().remoteAddress()+"】写空闲（客户端）");
                    ctx.writeAndFlush(ping);
                    break;
                case ALL_IDLE:
                    log.info("【"+ctx.channel().remoteAddress()+"】读写空闲");
                    break;
            }
        }
    }

    public void dealPhoneOff(ChannelHandlerContext ctx) {

        try {
            // 1.校验设备是否合法
            PhoneDataEntity phoneDataEntity = ctx.channel().attr(PhoneConstant.PHONE_CHANNEL_KEY).get();
            if (PublicUtil.isNotEmpty(phoneDataEntity)) {
                String deviceNo = phoneDataEntity.getDeviceNo();
                String redisKey = RedisKeyUtil.getZnPhoneKey(phoneDataEntity.getDeviceNo());
                // 设备连接
                phoneDataEntity = (PhoneDataEntity) redisTemplate.opsForValue().get(redisKey);
                if (PublicUtil.isNotEmpty(phoneDataEntity)) {

                    phoneDataEntity.setStatus(DeviceStatusEnum.OFFLINE.getKey());
                    phoneDataEntity.setArming(PhoneConstant.ARMING_OFF);
                    redisTemplate.opsForValue().set(redisKey, phoneDataEntity);

                    phoneMessageHandler.savePhoneData(phoneDataEntity);

                    PhoneConstant.PHONE_CHANNEL_MAP.remove(deviceNo);
                } else {
                    log.info("=====> phone {} is be break", deviceNo);
                }
            } else {
                log.info("=====> phone channel not login.");
            }
            // 出现异常就关闭
        } catch (Exception e) {
            log.error("===> exception caught: {}", e.getMessage());
        }
        ctx.close();
    }

    /**
     * 对象转Byte数组
     * @param obj
     * @return
     */
    public static byte[] objectToByteArray(Object obj) {
        byte[] bytes = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        ObjectOutputStream objectOutputStream = null;
        try {
            byteArrayOutputStream = new ByteArrayOutputStream();
            objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(obj);
            objectOutputStream.flush();
            bytes = byteArrayOutputStream.toByteArray();

        } catch (IOException e) {
            System.err.println("objectToByteArray failed, " + e);
        } finally {
            if (objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (IOException e) {
                    System.err.println("close objectOutputStream failed, " + e);
                }
            }
            if (byteArrayOutputStream != null) {
                try {
                    byteArrayOutputStream.close();
                } catch (IOException e) {
                    System.err.println("close byteArrayOutputStream failed, " + e);
                }
            }

        }
        return bytes;
    }

    /**
     * Byte数组转对象
     * @param bytes
     * @return
     */
    public static Object byteArrayToObject(byte[] bytes) {
        Object obj = null;
        ByteArrayInputStream byteArrayInputStream = null;
        ObjectInputStream objectInputStream = null;
        try {
//            byteArrayInputStream = new ByteArrayInputStream(bytes);
//            objectInputStream = new ObjectInputStream(byteArrayInputStream);
//            obj = objectInputStream.readObject();

            ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new ByteArrayInputStream(bytes)));
            obj = ois.readObject();
        } catch (Exception e) {
            System.err.println("byteArrayToObject failed, " + e);
        } finally {
            if (byteArrayInputStream != null) {
                try {
                    byteArrayInputStream.close();
                } catch (IOException e) {
                    System.err.println("close byteArrayInputStream failed, " + e);
                }
            }
            if (objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    System.err.println("close objectInputStream failed, " + e);
                }
            }
        }
        return obj;
    }

    public String format(String str) {

        String regex = "(.{2})";
        str = str.replaceAll(regex, "$1 ");
        return str;
    }

    public void channelCloseByDeviceNo(String deviceNo) {

        Channel channel = PhoneConstant.PHONE_CHANNEL_MAP.get(deviceNo);
        if (PublicUtil.isNotEmpty(channel)) {
            channel.close();
            PhoneConstant.PHONE_CHANNEL_MAP.remove(deviceNo);
        }
    }
}
